﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using HighDiveEngine.Resources;

namespace HighDiveEngine.IoEngines
{
    

    /// <summary>
    /// Engine for sending and receiving messages over TCP.
    /// Use TcpTxMessage to send a message and subscribe to TcpRxMessageEvent to receive messages
    /// </summary> 
    public class TcpEngine
    {
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private readonly TcpListener _listener;

        /// <summary>
        /// Constructor for the TcpEngine
        /// </summary>
        /// <param name="ipAddress">The IP-Address for this server to listen on</param>
        /// <param name="messageDelegate">Messagedelegate that gets passed to TcpRxObject to forward incoming messages</param>
        /// <example>new TcpEngine("127.0.0.1:9059");</example>
        public TcpEngine(string ipAddress, TcpRxMessageDelegate messageDelegate, TcpTxConnectionFailedDelegate connectionFailedDelegate)
        {
            var ip = ipAddress.Split(':');
            _listener = new TcpListener(new IPEndPoint(IPAddress.Parse(ip[0]), Convert.ToInt32(ip[1])));
            TcpRxObject.SubscribeToEvent(messageDelegate);
            TcpTxObject.SubscribeToEvent(connectionFailedDelegate);
        }

        /// <summary>
        /// Thread function for the TcpEngine
        /// </summary>
        /// <example>new Thread(new TcpEngine("127.0.0.1:9059").TcpServerThread).Start();</example>
        public void TcpServerThread()
        {
            if (Log.IsInfoEnabled) { Log.Info("Starting TCP server , listening on: " + _listener.LocalEndpoint.ToString()); }
            _listener.Start();
            var listen = true;
            while (listen)
            {
                try
                {
                    while (_listener.Pending())
                        (new Thread(new TcpRxObject(_listener.AcceptTcpClient()).RxThread) { IsBackground = true } ).Start();
                }
                catch (ThreadAbortException)
                {
                    _listener.Stop();
                    listen = false;
                    if (Log.IsInfoEnabled) { Log.Info("Aborting TCP server thread"); }
                }
                catch (Exception e)
                {
                    throw new TcpEngineException(e.Message);
                }
            }
            if (Log.IsInfoEnabled) { Log.Info("Closing TCP server thread"); }
        }

        /// <summary>
        /// Sends a message over TCP
        /// </summary>
        /// <param name="msg">Message to send, format: "ip-address@port@message"</param>
        /// <example>tcpEngine.TcpTxMessage("127.0.0.1:9059@Hello World")</example>
        public void TcpTxMessage(string msg)
        {
            (new Thread(new TcpTxObject(msg).TxThread) { IsBackground = true} ).Start();
        }
        
        #region NestledClassTcpRxObject
        public delegate void TcpRxMessageDelegate(string msg);
        /// <summary>
        /// Object used by TcpEngine to handle receiving TCP-messages 
        /// </summary>
        private class TcpRxObject
        {
            //private readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

            /// <summary>
            /// Event fired when a message is received over TCP
            /// </summary>
            private static event TcpRxMessageDelegate TcpRxMessageEvent;

            private readonly TcpClient _client;

            public TcpRxObject(TcpClient client)
            {
                _client = client;
            }

            /// <summary>
            /// Function used to receive incoming TCP-messages
            /// </summary>
            public void RxThread()
            {
                log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                
                var bytes = new Byte[256];

                if (log.IsDebugEnabled) { log.Debug("[" + Thread.CurrentThread.ManagedThreadId + "] Receiving TCP message from: " + _client.Client.RemoteEndPoint); }
                try
                {
                    var stream = _client.GetStream();
                    int i;
                    while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        if (log.IsDebugEnabled) { log.Debug("Received message: " + Encoding.ASCII.GetString(bytes, 0 , i)); }
                        if (TcpRxMessageEvent != null)
                            TcpRxMessageEvent(_client.Client.RemoteEndPoint + "@" + Encoding.ASCII.GetString(bytes, 0, i));
                    }
                }
                catch
                {

                }
                finally
                {
                    if (log.IsDebugEnabled) { log.Debug("Leaving TcpRxThread "); }
                    _client.Close();
                }
            }

            public static void SubscribeToEvent(TcpRxMessageDelegate messageDelegate)
            {
                TcpRxMessageEvent = null;
                TcpRxMessageEvent += messageDelegate;
            }
        } 
        #endregion

        #region NestledClassTcpTxObject
        public delegate void TcpTxConnectionFailedDelegate(string ip);
        /// <summary>
        /// Object used by TcpEngine to handle sending TCP-messages 
        /// </summary>
        private class TcpTxObject
        {
            

            private readonly IPEndPoint _ipEndPoint;
            private readonly byte[] _msg;

            private static event TcpTxConnectionFailedDelegate TcpTxConnectionFailedEvent;

            /// <summary>
            /// Constructor for the TCP-transmit object
            /// </summary>
            /// <param name="txString">String to send, format: "ip-address:port@message"</param>
            /// <example>new TcpTxObject("127.0.0.1:9059@Hello World");</example>
            public TcpTxObject(string txString)
            {
                var txMsg = txString.Split('@');
                var ip = txMsg[0].Split(':');
                _ipEndPoint = new IPEndPoint(IPAddress.Parse(ip[0]), Convert.ToInt32(ip[1]));
                _msg = Encoding.ASCII.GetBytes(txMsg[1]);
            }

            /// <summary>
            /// Thread method for sending the message over TCP
            /// </summary>
            /// <example>new Thread(new TcpTxObject("127.0.0.1:9059@Hello World").TxThread).Start();</example>
            public void TxThread()
            {
                log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
                if (log.IsDebugEnabled) { log.Debug("Sending a message to " + _ipEndPoint); }
                var client = new TcpClient();
                try
                {
                    client.Connect(_ipEndPoint);
                    var stream = client.GetStream();
                    stream.Write(_msg, 0, _msg.Length);
                }
                catch
                {
                    if (TcpTxConnectionFailedEvent != null)
                        TcpTxConnectionFailedEvent(_ipEndPoint.ToString());
                }
                finally
                {
                    client.Close();
                }
                if (log.IsDebugEnabled) { log.Debug("[" + Thread.CurrentThread.ManagedThreadId + "] Finished sending a message"); }
            }

            public static void SubscribeToEvent(TcpTxConnectionFailedDelegate connectionFailedDelegate)
            {
                TcpTxConnectionFailedEvent = null;
                TcpTxConnectionFailedEvent += connectionFailedDelegate;
            }
        } 
        #endregion
    }
}
